﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Threading;
using System.Xml.Linq;
using SharePhone.Utility;
using FieldInfo = SharePhone.Common.List.FieldInfo;

namespace SharePhone.Core
{
    public enum ItemState
    {
        New,
        Persisted,
        Deleted
    }

    /// <summary>
    /// Used as a base class for all core objects to store parameter values.
    /// </summary>
    public class BaseCoreObject : FrameworkObject, INotifyPropertyChanged
    {
        #region Enums

        internal enum UpdateMode
        {
            None,
            Update,
            Delete
        }

        #endregion

        #region Variables

        internal UpdateMode UpdateType;
        private Dictionary<string, object> dirtyDictionary;
        private Dictionary<String, object> objectValues;
        private ItemState state;

        #endregion

        #region Constructors

        public BaseCoreObject()
        {
            objectValues = new Dictionary<string, object>();
            dirtyDictionary = new Dictionary<string, object>();
        }

        #endregion

        #region Properties      

        internal int updateId;

        /// <summary>
        /// Get the state of the item
        /// </summary>
        [CoreField(PopulateRule = CorePopulateRule.None)]
        public ItemState State
        {
            get
            {
                if (UniqueId == Guid.Empty)
                    state = ItemState.New;
                else if (state != ItemState.Deleted)
                    state = ItemState.Persisted;
                return state;
            }
            internal set { state = value; }
        }

        /// <summary>
        /// Returns the SPList this item belongs to. If this is a new item, this property won't be set until the item has been added to a list.
        /// </summary>
        [CoreField(PopulateRule = CorePopulateRule.None)]
        public object ParentList { get; internal set; }

        /// <summary>
        /// Returns the id of the object determined by the CoreIndex attribute.
        /// </summary>
        [CoreField(PopulateRule = CorePopulateRule.None)]
        public object Id
        {
            get
            {
                CoreIndexAttribute ci = CoreIndexAttribute.GetIndexAttribute(this);
                if (ci != null)
                {
                    try
                    {
                        PropertyInfo pi = GetType().GetProperty(ci.Property);
                        return pi.GetValue(this, null);
                    }
                    catch
                    {
                        return null;
                    }
                }
                return UniqueId;
            }
        }

        /// <summary>
        /// Returns the internal list id for the SPListItem represented by this object
        /// </summary>
        [CoreField(PopulateRule = CorePopulateRule.None)]
        public int SPListItemId
        {
            get
            {
                return ReadObjectValue<int>("spListItemId");
            }
            set
            {
                WriteObjectValue("spListItemId", value);
                OnPropertyChanged("SPListItemId");
            }
        }

        /// <summary>
        /// Gets/sets the filename of the item 
        /// </summary>
        [CoreField(FieldName = "FileRef", PopulateRule = CorePopulateRule.SharepointReadOnly,
            Importance = FieldImportance.Optional)]
        public string FileRef { get; set; }

        /// <summary>
        /// Returns the unique id for the SPListItem represented by this object
        /// </summary>
        [CoreField(PopulateRule = CorePopulateRule.None)]
        public Guid UniqueId
        {
            get
            {
                try
                {
                    return ReadObjectValue<Guid>("uniqueId");
                }
                catch (BaseCoreException)
                {
                    throw;
                }
            }
            internal set
            {
                WriteObjectValue("uniqueId", value);
                OnPropertyChanged("UniqueId");
            }
        }

        #endregion

        #region Sharepoint write/read methods

        /// <summary>
        /// Reads a value from storage
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        /// <exception cref="BaseCoreException"></exception>
        protected T ReadObjectValue<T>(String key)
        {
            if (objectValues == null)
                throw new BaseCoreException(String.Format("No values found for {0}, storage not initiated", key));
            if (objectValues.ContainsKey(key.ToLower()))
            {
                object retVal = objectValues[key.ToLower()];
                try
                {
                    return (T) Convert.ChangeType(retVal, typeof (T), Thread.CurrentThread.CurrentCulture);
                }
                catch (InvalidCastException ice)
                {
                    throw new BaseCoreException(String.Format("[{0}] Error during conversion: {1}", key, ice.Message));
                }
                catch (ArgumentNullException ane)
                {
                    throw new BaseCoreException(String.Format("[{0}] Invalid value specified: {1}", key, ane.Message));
                }
            }
            return default(T);
        }

        /// <summary>
        /// Writes a value to storage
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        protected void WriteObjectValue(String key, object value)
        {
            if (objectValues == null)
                objectValues = new Dictionary<string, object>();
            if (!String.IsNullOrEmpty(key))
            {
                key = key.ToLower();
                if (objectValues.ContainsKey(key))
                    objectValues.Remove(key);
                objectValues.Add(key, value);
            }
        }

        internal XElement GenerateWebServiceUpdateFragment(int id)
        {
            updateId = id;
            var method = new XElement("Method");
            switch (UpdateType)
            {
                case UpdateMode.Update:
                    bool isNew = (SPListItemId == 0);
                    method.SetAttributeValue("ID", id);
                    method.SetAttributeValue("Cmd", string.Format("{0}", (isNew ? "New" : "Update")));
                    var field = new XElement("Field");
                    field.SetAttributeValue("Name", "ID");
                    field.SetValue(string.Format("{0}",
                                                 (isNew
                                                      ? "New"
                                                      : (SPListItemId == -1 ? string.Empty : SPListItemId.ToString()))));
                    PropertyInfo[] props = GetType().GetProperties();
                    method.Add(field);
                    Dictionary<string, object> values = GetMetaInfo();
                    foreach (string fieldName in values.Keys)
                    {
                        field = new XElement("Field");
                        field.SetAttributeValue("Name", string.Format("{0}", fieldName));
                        field.SetValue(string.Format("{0}", values[fieldName]));
                        method.Add(field);
                    }
                    break;
                case UpdateMode.Delete:
                    method.SetAttributeValue("Cmd", "Delete");
                    method.SetAttributeValue("ID", id);
                    field = new XElement("Field", new XAttribute("Name", "ID"));
                    field.SetValue(SPListItemId);
                    method.Add(field);
                    field = new XElement("Field", new XAttribute("Name", "FileRef"));
                    field.SetValue(FileRef);
                    method.Add(field);
                    break;
                default:
                    return null;
            }
            return method;
        }

        internal Dictionary<string, object> GetMetaInfo()
        {
            var values = new Dictionary<string, object>();
            PropertyInfo[] props = GetType().GetProperties();
            foreach (PropertyInfo pInfo in props)
            {
                object[] attributes = pInfo.GetCustomAttributes(typeof (CoreFieldAttribute), false);
                if (attributes != null && attributes.Length > 0)
                {
                    var cfa = attributes[0] as CoreFieldAttribute;
                    if (cfa != null)
                        if (cfa.PopulateRule == CorePopulateRule.SharepointReadOnly ||
                            cfa.PopulateRule == CorePopulateRule.None)
                            continue;
                    object val = pInfo.GetValue(this, null);
                    if (val == null)
                        continue;
                    if (pInfo.PropertyType == typeof (DateTime))
                    {
                        val = SPUtility.CreateISO8601DateTimeFromSystemDateTime((DateTime) val);
                    }
                    if (cfa != null) values.Add(cfa.FieldName, val);
                }
            }
            return values;
        }

        internal List<FieldInformation> GetFieldInformation(Dictionary<string, FieldInfo> fieldInfo)
        {
            var fields = new List<FieldInformation>();
            PropertyInfo[] props = GetType().GetProperties();
            foreach (PropertyInfo pInfo in props)
            {
                object[] attributes = pInfo.GetCustomAttributes(typeof (CoreFieldAttribute), false);
                if (attributes == null || attributes.Length <= 0) continue;
                var cfa = attributes[0] as CoreFieldAttribute;
                if (cfa != null)
                    if (cfa.PopulateRule == CorePopulateRule.SharepointReadOnly ||
                        cfa.PopulateRule == CorePopulateRule.None)
                        continue;
                var fi = new FieldInformation();
                if (cfa != null)
                {
                    FieldInfo fInfo = fieldInfo[cfa.FieldName];
                    fi.InternalName = fInfo.InternalName;
                    fi.Id = new Guid(fInfo.ID);
                    fi.DisplayName = fInfo.DisplayName;
                }
                object val = pInfo.GetValue(this, null);
                if (val == null) continue;
                fi.Value = val.ToString();
                fi.Type = FieldType.Text;
                fields.Add(fi);
            }
            return fields;
        }

        #endregion

        #region Methods

        public void Delete()
        {
            UpdateType = UpdateMode.Delete;
        }

        public void Update()
        {
            if (UpdateType != UpdateMode.Delete)
                UpdateType = UpdateMode.Update;
        }

        protected void SetPropertyValueDirty(string propertyName)
        {
            object oldValue = GetType().GetProperty(propertyName).GetValue(this, null);
            if (dirtyDictionary.ContainsKey(propertyName))
                dirtyDictionary[propertyName] = oldValue;
            else
                dirtyDictionary.Add(propertyName, oldValue);
            Update();
        }

        public object GetOldValueForProperty(string propertyName)
        {
            if (dirtyDictionary.ContainsKey(propertyName))
                return dirtyDictionary[propertyName];
            throw new ArgumentOutOfRangeException(propertyName);
        }

        internal void SetAllPropertiesAsClean()
        {
            dirtyDictionary = new Dictionary<string, object>();
            if (UpdateType != UpdateMode.Delete)
                UpdateType = UpdateMode.None;
        }

        internal void CleanProperty(string propertyName)
        {
            if (dirtyDictionary.ContainsKey(propertyName))
                dirtyDictionary.Remove(propertyName);
        }

        public bool IsPropertyValueDirty(string propertyName)
        {
            return dirtyDictionary.ContainsKey(propertyName);
        }

        public void ResetValues()
        {
            foreach (string property in dirtyDictionary.Keys)
            {
                ResetPropertyValue(property);
            }
        }

        public void ResetPropertyValue(string propertyName)
        {
            PropertyInfo pInfo = GetType().GetProperty(propertyName);
            if (pInfo == null) return;
            pInfo.SetValue(this, GetOldValueForProperty(propertyName), null);
            CleanProperty(propertyName);
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}